﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace AWSS3ToAzureBlob
{
    public class FileMigrator : IFileMigrator
    {
        private string _awsS3Bucket;
        private string _awsAccessKey;
        private string _awsSecretKey;
        private int _awsExpiryMinutes;
        private string _azureStorageAccount;
        private string _azureStorageKey;
        private string _azureStorageContainer;
        private IAWSS3Facade _awsS3;
        private IAzureStorageBlobFacade _azureStorageBlob;
        public void Initialize(string awsS3Bucket,
            string awsAccessKey,
            string awsSecretKey,
            int awsExpiryMinutes,
            string azureStorageAccount,
            string azureStorageKey,
            string azureStorageContainer,
            IAWSS3Facade awsS3,
            IAzureStorageBlobFacade azureStorageBlob)
        {
            _awsS3Bucket = awsS3Bucket;
            _awsAccessKey = awsAccessKey;
            _awsSecretKey = awsSecretKey;
            _awsExpiryMinutes = awsExpiryMinutes;
            _azureStorageAccount = azureStorageAccount;
            _azureStorageKey = azureStorageKey;
            _azureStorageContainer = azureStorageContainer;
            _awsS3 = awsS3;
            _azureStorageBlob = azureStorageBlob;
        }

        public void MigrateFiles()
        {
            TraceHelper.TraceInformation(Messages.FileMigrationStarted);

            ValidateConnections();
            S3ObjectWithUrl[] s3Objects = GetS3ObjectsWithUrl();
            string[] azureUrls = StartCopy(s3Objects);
            WaitCopiesToComplete();

            TraceHelper.TraceInformation(Messages.FileMigrationCompleted);
        }

        private void WaitCopiesToComplete()
        {
            bool pendingCopy = true;

            while (pendingCopy)
            {
                pendingCopy = false;

                try
                {
                    var copies = _azureStorageBlob.GetPendingCopies();

                    foreach (var copy in copies)
                    {
                        if (copy.Status == CopyStatus.Pending)
                        {
                            pendingCopy = true;
                        }
                        else if (copy.Status == CopyStatus.Failed)
                        {
                            pendingCopy = true;
                            RestartCopy(copy.Url);
                        }

                        LogCopyStatus(copy);
                    }
                }
                catch (Exception ex)
                {
                    pendingCopy = true;
                    TraceHelper.TraceError("[WaitCopiesToComplete] Error getting pending copies: {0}", ex.Message);
                }

                #if !MOCK
                Thread.Sleep(10000);
                #endif
            }
        }

        private void LogCopyStatus(CopyState copyState)
        {
            TraceHelper.TraceInformation("{0} - {1} - {2} - {3}",
                copyState.Url,
                copyState.Status,
                copyState.BytesCopied,
                copyState.TotalBytes);
        }

        private void RestartCopy(string url)
        {
            TraceHelper.TraceInformation("Restartring copy: {0}", url);

            _azureStorageBlob.RestartCopy(url);
        }

        private string[] StartCopy(S3ObjectWithUrl[] s3Objects)
        {
            int length = s3Objects.Length;
            List<string> azureUrls = new List<string>();

            for (int i = 0; i < length; i++)
            {
                TraceHelper.TraceVerbose("Starting copy: {0}", s3Objects[i].Key);
                TraceHelper.TraceVerbose("Source url: {0}", s3Objects[i].Url);

                try
                {
                    string azureUrl = _azureStorageBlob.StartCopy(s3Objects[i].Key,
                        s3Objects[i].Url);

                    azureUrls.Add(azureUrl);
                }
                catch (Exception ex)
                {
                    TraceHelper.TraceError("[StartCopy] Error copying {0} - {1}", s3Objects[i].Key, ex.Message);
                }
            }

            return azureUrls.ToArray();
        }

        private S3ObjectWithUrl[] GetS3ObjectsWithUrl()
        {
            string[] s3Keys = ListS3Keys();
            TraceHelper.TraceInformation("#S3 objects found: {0}", s3Keys.Length);

            S3ObjectWithUrl[] s3Objects = GetS3Urls(s3Keys);

            return s3Objects;
        }

        private S3ObjectWithUrl[] GetS3Urls(string[] s3Keys)
        {
            List<S3ObjectWithUrl> s3Objects = new List<S3ObjectWithUrl>();

            foreach (var s3Key in s3Keys)
            {
                try
                {
                    TraceHelper.TraceVerbose("[GetS3Urls] Generating url for key: {0}", s3Key);
                    string url = _awsS3.GenerateUrl(s3Key);

                    S3ObjectWithUrl s3Object = new S3ObjectWithUrl()
                    {
                        Key = s3Key,
                        Url = url
                    };

                    s3Objects.Add(s3Object);
                }
                catch (Exception ex)
                {
                    TraceHelper.TraceError("[GetS3Urls] {0}", ex.Message);
                }
            }

            return s3Objects.ToArray();
        }

        private string[] ListS3Keys()
        {
            string[] s3Keys = _awsS3.ListKeys();

            if (s3Keys == null ||
                s3Keys.Length == 0)
            {
                throw new ApplicationException(Messages.NoObjectsOnBucketWereFound);
            }

            return s3Keys;
        }

        private void ValidateConnections()
        {
            if (!_awsS3.Initialize(_awsAccessKey,
                _awsSecretKey,
                _awsS3Bucket,
                _awsExpiryMinutes))
            {
                throw new ApplicationException(Messages.AWSS3ConnectionIssues);
            }

            if (!_azureStorageBlob.Initialize(_azureStorageAccount,
                _azureStorageKey,
                _azureStorageContainer))
            {
                throw new ApplicationException(Messages.AzureStorageBlobConnectionIssues);
            }
        }
    }
}